/**
 * 服务器模块
 * 搭建一个websocket服务器，实现网络通信
 * 服务器针对不同请求进行不同的处理
 */

#pragma once 
#include"Detail.hpp"
#include<list>
#include<condition_variable>
#include<mutex>
#include<thread>
#include"RoomManager.hpp"
#include"DataBase.hpp"
#include"OnlineManager.hpp"
#include"SessionManager.hpp"
#include"MatchManager.hpp"

namespace OnlineGoBang
{

    const std::string webroot="./wwwroot/";
    using message_ptr=wsserver_t::message_ptr;
    using wsserver_ptr=std::shared_ptr<wsserver_t>;
    using SessionManagerPtr=std::shared_ptr<SessionManager>;
    using MatchManagerPtr=std::shared_ptr<MatchManager>;
    //服务器类
    class Server
    {
        void testhttp(websocketpp::connection_hdl handler)
        {
            wsserver_t::connection_ptr con=_wsserverp->get_con_from_hdl(handler);
            websocketpp::http::parser::request req=con->get_request();
            std::string method=req.get_method();
            std::string uri=req.get_uri();
            std::string pathname=_webroot+uri;
            std::string body;
            Detail::ReadFile::Read(pathname,body);

            con->set_body(body);
            con->set_status(websocketpp::http::status_code::ok);
            con->append_header("Content-Length",std::to_string(body.size()));
        }
    

        //静态资源请求处理
        void FileHandler(wsserver_t::connection_ptr& con)
        {
            //获取请求信息
            websocketpp::http::parser::request req=con->get_request();
            std::string uri=req.get_uri();
            //组合出真实文件路径
            std::string realpath=_webroot+uri;
            if(realpath.back()=='/')//请求真实路径是一个目录
            {
                realpath=_webroot+"login.html";
            }
            //读取文件内容
            std::string body;
            bool ret=Detail::ReadFile::Read(realpath,body);
            if(!ret)
            {
                body="<html><head><meta charset=\"UTF-8\"></head><body><h1>Not Found</h1></body>";
                con->set_status(websocketpp::http::status_code::not_found);
                con->set_body(body);
                con->append_header("Content-Length",std::to_string(body.size()));
                return;
            }
            con->set_body(body);
            con->set_status(websocketpp::http::status_code::ok);
            con->append_header("Content-Length",std::to_string(body.size()));
        }

        //设置响应信息
        void SetHttpResponse(wsserver_t::connection_ptr& con,bool flag,const std::string& reason
                        ,websocketpp::http::status_code::value code)
        {
            Json::Value resp;
            std::string resp_body;
            resp["result"]=flag;
            resp["reason"]=reason;
            Detail::JSON::Serialize(resp,resp_body);
            con->set_status(code);
            con->set_body(resp_body);
            con->append_header("Content-Type","application/json");
            con->append_header("Content-Length",std::to_string(resp_body.size()));
        }
        //用户注册请求
        void Register(wsserver_t::connection_ptr& con)
        {
            //获取请求信息s
            std::string req_body=con->get_request_body();
            //对请求正文反序列化，取出注册信息
            Json::Value reg_info;
            bool ret=Detail::JSON::UnSerialize(req_body,reg_info);
            if(!ret)//反序列化失败，请求格式错误
            {
                LOG(DEBUG,"请求反序列化失败\n");
                SetHttpResponse(con,false,"请求格式错误",websocketpp::http::status_code::bad_request);
                return;
            }

            //新增用户
            if(reg_info["username"].isNull()||reg_info["password"].isNull())
            {
                LOG(DEBUG,"用户名或密码缺失\n");
                SetHttpResponse(con,false,"用户名或密码缺失",websocketpp::http::status_code::bad_request);
                return;
            }
            ret=_utp->Insert(reg_info);
            if(!ret)
            {
                //LOG(DEBUG,"用户已存在\n");
                SetHttpResponse(con,false,"注册用户失败",websocketpp::http::status_code::bad_request);
                return;
            }
            //注册用户成功
            SetHttpResponse(con,true,"用户注册成功",websocketpp::http::status_code::ok);

        }

        //用户登录请求
        void Login(wsserver_t::connection_ptr& con)
        {
            //获取请求信息
            std::string req_body=con->get_request_body();
            //对请求正文反序列化，取出注册信息
            Json::Value login_info;
            bool ret=Detail::JSON::UnSerialize(req_body,login_info);
            if(!ret)//反序列化失败，请求格式错误
            {
                LOG(DEBUG,"请求反序列化失败\n");
                SetHttpResponse(con,false,"请求格式错误",websocketpp::http::status_code::bad_request);
                return;
            }

            //检查用户名和密码是否完整
            if(login_info["username"].isNull()||login_info["password"].isNull())
            {
                LOG(DEBUG,"用户名或密码缺失\n");
                SetHttpResponse(con,false,"用户名或密码缺失",websocketpp::http::status_code::bad_request);
                return;
            }
            //验证登录信息
            ret=_utp->Login(login_info);
            if(!ret)
            {
                LOG(DEBUG,"用户名或密码错误\n");
                SetHttpResponse(con,false,"用户名或密码错误",websocketpp::http::status_code::bad_request);
                return;
            }

            //创建Session信息
            uint64_t uid=login_info["id"].asUInt64();
            SessionPtr sp=_smp->CreateSession(uid,LOGIN);
            _smp->SetSessionExpireTime(sp->GetSid(),TIME_OUT);//设置Session为临时有效
            if(sp.get()==nullptr)
            {
                LOG(DEBUG,"创建会话失败\n");
                SetHttpResponse(con,false,"创建会话失败",websocketpp::http::status_code::internal_server_error);
            }
            //设置响应cookie信息
            std::string cookie_sid="SSID="+std::to_string(sp->GetSid());
            con->append_header("Set-Cookie",cookie_sid);
            SetHttpResponse(con,true,"登录成功",websocketpp::http::status_code::ok);
        }

        //从Cookie信息中取出session信息ssid
        bool GetSID(const std::string& cookie,const std::string& key,std::string& val)
        {
            const std::string cookiesep="; ";//Cookie中，各个信息的分隔符
            const std::string KVsep="=";//Cookie各个信息中KV的分隔符
            std::vector<std::string> kvs;
            Detail::StringSplit::Split(cookie,cookiesep,kvs);
            for(auto& kv:kvs)
            {
                //对每一个Cookie信息再次分割
                std::vector<std::string> res;
                Detail::StringSplit::Split(kv,KVsep,res);
                if(res.size()!=2)
                {
                    continue;
                }
                if(res[0]==key)
                {
                    val=res[1];
                    return true;
                }
            }
            return false;
        }
        //用户信息获取请求
        void Info(wsserver_t::connection_ptr& con)
        {
            //获取请求头中的cookie信息
            std::string cookie_str=con->get_request_header("Cookie");
            if(cookie_str.empty())//没有Cookie信息
            {
                SetHttpResponse(con,false,"请求中没有Cookie信息",websocketpp::http::status_code::bad_request);
                return;
            }
            //从Cookie中取出session信息ssid
            std::string sid_str;
            bool ret=GetSID(cookie_str,"SSID",sid_str);
            if(!ret)//Cookie中没有session信息
            {
                SetHttpResponse(con,false,"没有SSID信息，请重新登陆",websocketpp::http::status_code::bad_request);
                return;
            }
            //通过sid找session信息
            SessionPtr sp=_smp->GetSession(std::stol(sid_str));
            if(sp.get()==nullptr)//没有找到session信息，session已过期，需要重新登陆
            {
                SetHttpResponse(con,false,"session已过期，请重新登陆",websocketpp::http::status_code::bad_request);
                return;
            }
            //根据session信息取出用户数据
            uint64_t uid=sp->GetUser();
            Json::Value user_info;
            ret=_utp->Select_by_ID(uid,user_info);
            if(!ret)//获取用户信息失败
            {
                SetHttpResponse(con,false,"获取用户数据失败，用户不存在",websocketpp::http::status_code::bad_request);
                return;
            }
            //序列化用户数据发送给客户端
            std::string body;
            Detail::JSON::Serialize(user_info,body);
            con->set_body(body);
            con->append_header("Content-Type","application/json");
            con->append_header("Content-Length",std::to_string(body.size()));
            con->set_status(websocketpp::http::status_code::ok);
            //重置定时任务
            _smp->SetSessionExpireTime(sp->GetSid(),TIME_OUT);
        }   
        //收到http请求的回调
        void Http_Callback(websocketpp::connection_hdl handler)
        {
            // testhttp(handler);
            wsserver_t::connection_ptr con=_wsserverp->get_con_from_hdl(handler);
            websocketpp::http::parser::request req=con->get_request();
            std::string method=req.get_method();
            std::string uri=req.get_uri();
            LOG(INFO,"method:%s  uri:%s\n",method.c_str(),uri.c_str());
            if(method=="POST"&&uri=="/reg")
            {
                LOG(INFO,"注册请求\n");
                Register(con);
            }
            else if(method=="POST"&&uri=="/login")
            {
                LOG(INFO,"登录请求\n");
                Login(con);
            }
            else if(method=="GET"&&uri=="/info")
            {
                LOG(INFO,"获取信息请求\n");
                Info(con);
            }
            else
            {
                LOG(INFO,"静态资源请求\n");
                FileHandler(con);
            }
        }

        //取出Cookie中的session信息
        SessionPtr GetSessionByCookie(wsserver_t::connection_ptr& con)
        {
            //获取请求头中的cookie信息
            std::string cookie_str=con->get_request_header("Cookie");
            if(cookie_str.empty())//没有Cookie信息
            {
                SetWebSocketResponse(con,"hall_ready",false,"没有找到Cookie信息，请重新登陆");
                return SessionPtr();
            }
            //从Cookie中取出session信息ssid
            std::string sid_str;
            bool ret=GetSID(cookie_str,"SSID",sid_str);
            if(!ret)//Cookie中没有session信息
            {
                SetWebSocketResponse(con,"hall_ready",false,"没有找到SSID信息，请重新登陆");
                return SessionPtr();
            }
            //通过sid找session信息
            SessionPtr sp=_smp->GetSession(std::stol(sid_str));
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                SetWebSocketResponse(con,"hall_ready",false,"没有找到session信息，请重新登陆");
                return SessionPtr();
            }
            return sp;
        }

        //设置游戏大厅相关的响应
        void SetWebSocketResponse(wsserver_t::connection_ptr& con,const std::string& optype
                                ,bool result,const std::string& reason)
        {
            Json::Value resp;
            resp["optype"]=optype;
            resp["reason"]=reason;
            resp["result"]=result;
            std::string body;
            Detail::JSON::Serialize(resp,body);
            con->send(body);
        }
        //游戏大厅websocket长连接建立成功时的回调
        void WSOpen_GameHall(wsserver_t::connection_ptr& con)
        {
            //登录验证---只有登录成功的用户才有session信息
            //获取session信息
            SessionPtr sp=GetSessionByCookie(con);
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                return ;
            }
            //判断当前用户是否重复登录
            uint64_t uid=sp->GetUser();
            if(_omp->InHall(uid)||_omp->InRoom(uid))
            {
                SetWebSocketResponse(con,"hall_ready",false,"当前用户重复登录");
                return;
            }
            //将当前用户及连接加入到游戏大厅
            _omp->EnterGameHall(uid,con);
            //给客户端响应游戏大厅建立成功
            SetWebSocketResponse(con,"hall_ready",true,"游戏大厅建立成功");
            //设置session为永久存在
            _smp->SetSessionExpireTime(sp->GetSid(),TIME_FOREVER);
        }
        //游戏房间websocket长连接建立成功时的回调
        void WSOpen_GameRoom(wsserver_t::connection_ptr& con)
        {   
            //获取session信息
            SessionPtr sp=GetSessionByCookie(con);
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                return ;
            }
            //判断当前用户是否重复登录(在线用户管理的房间中是否已有当前用户信息)
            uint64_t uid=sp->GetUser();
            if(_omp->InHall(uid)||_omp->InRoom(uid))
            {
                SetWebSocketResponse(con,"room_ready",false,"当前用户重复登录");
                return;
            }
            //判断当前用户是否已创建的房间---房间管理
            RoomPtr rp=_rmp->GetRoomByUid(sp->GetUser());
            if(rp.get()==nullptr)
            {
                SetWebSocketResponse(con,"room_ready",false,"当前玩家没有已创建的房间");
                return;
            }
            //将用户添加到在线用户管理的游戏房间中
            _omp->EnterGameRoom(sp->GetUser(),con);
            //重置session信息为永久有效
            _smp->SetSessionExpireTime(sp->GetSid(),TIME_FOREVER);
            //组织房间准备完毕的响应
            //SetWebSocketResponse(con,"room_ready",true,"游戏房间准备完毕！");
            Json::Value resp;
            resp["optype"]="room_ready";
            resp["result"]="游戏房间准备完毕！";
            resp["room_id"]=(Json::UInt64)rp->RoomID();
            resp["uid"]=(Json::UInt64)sp->GetUser();
            resp["white_id"]=(Json::UInt64)rp->GetWhiteUser();
            resp["black_id"]=(Json::UInt64)rp->GetBlackUser();
            std::string body;
            Detail::JSON::Serialize(resp,body);
            con->send(body);
        }
        //websocket连接建立成功的回调
        void WSOpen_Callback(websocketpp::connection_hdl handler)
        {
            wsserver_t::connection_ptr con=_wsserverp->get_con_from_hdl(handler);
            websocketpp::http::parser::request req=con->get_request();
            std::string method=req.get_method();
            std::string uri=req.get_uri();
            LOG(DEBUG,"method:%s  uri:%s\n",method.c_str(),uri.c_str());
            if(uri=="/hall")//游戏大厅
            {
                LOG(INFO,"请求建立游戏大厅\n");
                WSOpen_GameHall(con);
            }
            else if(uri=="/room")//游戏房间
            {
                LOG(INFO,"请求建立游戏房间\n");
                WSOpen_GameRoom(con);
            }
        }

        //游戏大厅websocket长连接关闭时的回调
        void WSClose_GameHall(wsserver_t::connection_ptr& con)
        {
            //获取session信息
            SessionPtr sp=GetSessionByCookie(con);
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                return ;
            }

            //用户退出大厅----在线用户管理
            _omp->ExitGameHall(sp->GetUser());
            //重新设置过期时间
            //LOG(DEBUG,"重新设置过期时间\n");
            _smp->SetSessionExpireTime(sp->GetSid(),TIME_OUT);
            //将用户从匹配队列中移除(如果在匹配对列中)
            _mmp->Del(sp->GetUser());
        }
        //游戏房间websocket长连接关闭时的回调
        void WSClose_GameRoom(wsserver_t::connection_ptr& con)
        {
            //获取session信息
            SessionPtr sp=GetSessionByCookie(con);
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                return ;
            }

            //用户退出房间---在线用户管理
            _omp->ExitGameRoom(sp->GetUser());
            //重新设置过期时间
            _smp->SetSessionExpireTime(sp->GetSid(),TIME_OUT);

            //将用户从房间中移除---房间管理
            _rmp->RemoveUser(sp->GetUser());
        }
        //websocket关闭连接的回调
        void WSClose_Callback(websocketpp::connection_hdl handler)
        {
            wsserver_t::connection_ptr con=_wsserverp->get_con_from_hdl(handler);
            websocketpp::http::parser::request req=con->get_request();
            std::string method=req.get_method();
            std::string uri=req.get_uri();
            LOG(DEBUG,"method:%s  uri:%s\n",method.c_str(),uri.c_str());
            if(uri=="/hall")//游戏大厅连接关闭
            {
                LOG(INFO,"游戏大厅连接关闭\n");
                WSClose_GameHall(con);
            }
            else if(uri=="/room")//游戏房间连接关闭
            {
                LOG(INFO,"游戏房间连接关闭\n");
                WSClose_GameRoom(con);
            }
        }

        //websocket游戏大厅长连接收到消息时的回调
        void WSMsg_GameHall(wsserver_t::connection_ptr& con,message_ptr& msg)
        {
            //获取session信息,进行用户身份认证
            SessionPtr sp=GetSessionByCookie(con);
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                return ;
            }
            LOG(DEBUG,"开始解析请求消息\n");
            //获取请求信息
            std::string req_body=msg->get_payload();
            Json::Value req;
            bool ret=Detail::JSON::UnSerialize(req_body,req);
            if(!ret)
            {
                SetWebSocketResponse(con,"unknow",false,"消息请求解析失败");
                return;
            }
            //处理消息请求
            if(!req["optype"].isNull()&&req["optype"].asString()=="match_start")
            {
                //开始匹配，把用户添加到对应的匹配队列中
                _mmp->Add(sp->GetUser());
                SetWebSocketResponse(con,"match_start",true,"开始匹配");
                return;
            }
            else if(!req["optype"].isNull()&&req["optype"].asString()=="match_stop")
            {
                //停止匹配，把用户添加从对应的匹配队列中移除
                _mmp->Del(sp->GetUser());
                SetWebSocketResponse(con,"match_stop",true,"停止匹配");
                return;
            }
            //未知错误
            SetWebSocketResponse(con,"unknow",false,"未知类型");
        }
        //websocket游戏房间长连接收到消息时的回调
        void WSMsg_GameRoom(wsserver_t::connection_ptr& con,message_ptr& msg)
        {
            //获取session信息
            SessionPtr sp=GetSessionByCookie(con);
            if(sp.get()==nullptr)//没有找到session信息，session已过期/销毁，需要重新登陆
            {
                return ;
            }
            //获取用户房间信息
            RoomPtr rp=_rmp->GetRoomByUid(sp->GetUser());
            if(rp.get()==nullptr)
            {
                SetWebSocketResponse(con,"unknow",false,"当前玩家没有已创建的房间");
                return;
            }
            //对消息进行反序列化
            Json::Value req_json;
            std::string req_body=msg->get_payload();
            bool ret=Detail::JSON::UnSerialize(req_body,req_json);
            if(!ret)
            {
                SetWebSocketResponse(con,"unknow",false,"消息请求解析失败");
                return;
            }
            //通过房间管理模块对请求消息进行处理
            rp->HandleRequest(req_json);
        }
        //websocket收到消息的回调
        void WSMsg_Callback(websocketpp::connection_hdl handler,message_ptr msg)
        {
            wsserver_t::connection_ptr con=_wsserverp->get_con_from_hdl(handler);
            websocketpp::http::parser::request req=con->get_request();
            std::string method=req.get_method();
            std::string uri=req.get_uri();
            LOG(DEBUG,"method:%s  uri:%s\n",method.c_str(),uri.c_str());
            if(uri=="/hall")//游戏大厅收到消息
            {
                LOG(INFO,"游戏大厅收到消息\n");
                WSMsg_GameHall(con,msg);
            }
            else if(uri=="/room")//游戏房间收到消息
            {
                LOG(INFO,"游戏房间收到消息\n");
                WSMsg_GameRoom(con,msg);
            } 
        }

        
        //初始化websocket服务器
        void Init()
        {
            //设置日志等级
            _wsserverp->set_access_channels(websocketpp::log::alevel::none);//默认不输出日志信息
            //初始化asio调度器
            _wsserverp->init_asio();
            //启用地址重用
            _wsserverp->set_reuse_addr(true);
            //设置回调函数
            _wsserverp->set_open_handler(std::bind(&Server::WSOpen_Callback,this,std::placeholders::_1));
            _wsserverp->set_close_handler(std::bind(&Server::WSClose_Callback,this,std::placeholders::_1));
            _wsserverp->set_http_handler(std::bind(&Server::Http_Callback,this,std::placeholders::_1));
            _wsserverp->set_message_handler(std::bind(&Server::WSMsg_Callback,this,std::placeholders::_1,std::placeholders::_2));
        }

    public:
        Server (const std::string &host,
                const std::string &username,
                const std::string &password,
                const std::string &dbname,
                uint16_t port = 3306,
                const std::string& wb=webroot)
            :_webroot(wb)
            ,_utp(new UserTable(host,username,password,dbname,port))
            ,_omp(new OnlineManager())
            ,_rmp(new RoomManager(_utp,_omp))
            ,_wsserverp(new wsserver_t())
            ,_smp(new SessionManager(_wsserverp))
            ,_mmp(new MatchManager(_rmp,_utp,_omp))
        {
            Init();
        }
        Server(UserTablePtr& utp,OnlineManagerPtr& omp,RoomManagerPtr& rmp,wsserver_ptr& wss
            ,SessionManagerPtr& smp,MatchManagerPtr& mmp,const std::string& wb=webroot)
            :_webroot(wb),_utp(utp),_omp(omp),_rmp(rmp),_wsserverp(wss),_smp(smp),_mmp(mmp)
        {
            Init();
        }
        void Start(int port)
        {
            //设置监听端口
            _wsserverp->listen(port);
            //获取连接
            _wsserverp->start_accept();
            //启动服务
            _wsserverp->run();
        }
    private:
        std::string _webroot;//静态资源根目录
        UserTablePtr _utp;
        OnlineManagerPtr _omp;
        RoomManagerPtr _rmp;
        wsserver_ptr  _wsserverp;
        SessionManagerPtr _smp;
        MatchManagerPtr _mmp;
    };
}